package com.cloudcc.cloudframe.util;

/**
 * <p>
 * Operations on {@link CharSequence} that are {@code null} safe.
 * </p>
 * 
 * @see CharSequence
 * @since 3.0
 * @version $Id: CharSequenceUtils.java 1469220 2013-04-18 08:15:47Z bayard $
 */
public class CharSequenceUtils {

	/**
	 * <p>
	 * {@code CharSequenceUtils} instances should NOT be constructed in standard
	 * programming.
	 * </p>
	 * 
	 * <p>
	 * This constructor is public to permit tools that require a JavaBean
	 * instance to operate.
	 * </p>
	 */
	public CharSequenceUtils() {
		super();
	}

	// -----------------------------------------------------------------------
	/**
	 * <p>
	 * Returns a new {@code CharSequence} that is a subsequence of this sequence
	 * starting with the {@code char} value at the specified index.
	 * </p>
	 * 
	 * <p>
	 * This provides the {@code CharSequence} equivalent to
	 * {@link String#substring(int)}. The length (in {@code char}) of the
	 * returned sequence is {@code length() - start}, so if {@code start == end}
	 * then an empty sequence is returned.
	 * </p>
	 * 
	 * @param cs
	 *            the specified subsequence, null returns null
	 * @param start
	 *            the start index, inclusive, valid
	 * @return a new subsequence, may be null
	 * @throws IndexOutOfBoundsException
	 *             if {@code start} is negative or if {@code start} is greater
	 *             than {@code length()}
	 */
	public static CharSequence subSequence(final CharSequence cs,
			final int start) {
		return cs == null ? null : cs.subSequence(start, cs.length());
	}

	// -----------------------------------------------------------------------
	/**
	 * <p>
	 * Finds the first index in the {@code CharSequence} that matches the
	 * specified character.
	 * </p>
	 * 
	 * @param cs
	 *            the {@code CharSequence} to be processed, not null
	 * @param searchChar
	 *            the char to be searched for
	 * @param start
	 *            the start index, negative starts at the string start
	 * @return the index where the search char was found, -1 if not found
	 */
	static int indexOf(final CharSequence cs, final int searchChar, int start) {
		if (cs instanceof String) {
			return ((String) cs).indexOf(searchChar, start);
		} else {
			final int sz = cs.length();
			if (start < 0) {
				start = 0;
			}
			for (int i = start; i < sz; i++) {
				if (cs.charAt(i) == searchChar) {
					return i;
				}
			}
			return -1;
		}
	}

	/**
	 * Used by the indexOf(CharSequence methods) as a green implementation of
	 * indexOf.
	 * 
	 * @param cs
	 *            the {@code CharSequence} to be processed
	 * @param searchChar
	 *            the {@code CharSequence} to be searched for
	 * @param start
	 *            the start index
	 * @return the index where the search sequence was found
	 */
	static int indexOf(final CharSequence cs, final CharSequence searchChar,
			final int start) {
		return cs.toString().indexOf(searchChar.toString(), start);
		// if (cs instanceof String && searchChar instanceof String) {
		// // TODO: Do we assume searchChar is usually relatively small;
		// // If so then calling toString() on it is better than reverting to
		// // the green implementation in the else block
		// return ((String) cs).indexOf((String) searchChar, start);
		// } else {
		// // TODO: Implement rather than convert to String
		// return cs.toString().indexOf(searchChar.toString(), start);
		// }
	}

	/**
	 * <p>
	 * Finds the last index in the {@code CharSequence} that matches the
	 * specified character.
	 * </p>
	 * 
	 * @param cs
	 *            the {@code CharSequence} to be processed
	 * @param searchChar
	 *            the char to be searched for
	 * @param start
	 *            the start index, negative returns -1, beyond length starts at
	 *            end
	 * @return the index where the search char was found, -1 if not found
	 */
	static int lastIndexOf(final CharSequence cs, final int searchChar,
			int start) {
		if (cs instanceof String) {
			return ((String) cs).lastIndexOf(searchChar, start);
		} else {
			final int sz = cs.length();
			if (start < 0) {
				return -1;
			}
			if (start >= sz) {
				start = sz - 1;
			}
			for (int i = start; i >= 0; --i) {
				if (cs.charAt(i) == searchChar) {
					return i;
				}
			}
			return -1;
		}
	}

	/**
	 * Used by the lastIndexOf(CharSequence methods) as a green implementation
	 * of lastIndexOf
	 * 
	 * @param cs
	 *            the {@code CharSequence} to be processed
	 * @param searchChar
	 *            the {@code CharSequence} to be searched for
	 * @param start
	 *            the start index
	 * @return the index where the search sequence was found
	 */
	static int lastIndexOf(final CharSequence cs,
			final CharSequence searchChar, final int start) {
		return cs.toString().lastIndexOf(searchChar.toString(), start);
		// if (cs instanceof String && searchChar instanceof String) {
		// // TODO: Do we assume searchChar is usually relatively small;
		// // If so then calling toString() on it is better than reverting to
		// // the green implementation in the else block
		// return ((String) cs).lastIndexOf((String) searchChar, start);
		// } else {
		// // TODO: Implement rather than convert to String
		// return cs.toString().lastIndexOf(searchChar.toString(), start);
		// }
	}

	/**
	 * Green implementation of toCharArray.
	 * 
	 * @param cs
	 *            the {@code CharSequence} to be processed
	 * @return the resulting char array
	 */
	static char[] toCharArray(final CharSequence cs) {
		if (cs instanceof String) {
			return ((String) cs).toCharArray();
		} else {
			final int sz = cs.length();
			final char[] array = new char[cs.length()];
			for (int i = 0; i < sz; i++) {
				array[i] = cs.charAt(i);
			}
			return array;
		}
	}

	/**
	 * Green implementation of regionMatches.
	 * 
	 * @param cs
	 *            the {@code CharSequence} to be processed
	 * @param ignoreCase
	 *            whether or not to be case insensitive
	 * @param thisStart
	 *            the index to start on the {@code cs} CharSequence
	 * @param substring
	 *            the {@code CharSequence} to be looked for
	 * @param start
	 *            the index to start on the {@code substring} CharSequence
	 * @param length
	 *            character length of the region
	 * @return whether the region matched
	 */
	static boolean regionMatches(final CharSequence cs,
			final boolean ignoreCase, final int thisStart,
			final CharSequence substring, final int start, final int length) {
		if (cs instanceof String && substring instanceof String) {
			return ((String) cs).regionMatches(ignoreCase, thisStart,
					(String) substring, start, length);
		} else {
			int index1 = thisStart;
			int index2 = start;
			int tmpLen = length;

			while (tmpLen-- > 0) {
				char c1 = cs.charAt(index1++);
				char c2 = substring.charAt(index2++);

				if (c1 == c2) {
					continue;
				}

				if (!ignoreCase) {
					return false;
				}

				// The same check as in String.regionMatches():
				if (Character.toUpperCase(c1) != Character.toUpperCase(c2)
						&& Character.toLowerCase(c1) != Character
								.toLowerCase(c2)) {
					return false;
				}
			}

			return true;
		}
	}
}
